﻿using System;
using System.Diagnostics.Contracts;
using Microsoft.Practices.Unity;
using YasharEl.Infrastructure.ObjectFactory.Unity.LifeStyle;

namespace YasharEl.Infrastructure.ObjectFactory.Unity
{
    public class UnityDependencyRegistrar : UnityContainerExtension, IDependencyRegistrar
    {
        #region ReadOnly Fields

        protected IUnityContainer unityContainer;

        #endregion

        #region Constructors

        public UnityDependencyRegistrar(IUnityContainer unityContainer)
        {
            Contract.Requires<ArgumentNullException>(unityContainer != null, "unityContainer could not be null.");
            this.unityContainer = unityContainer;
            this.unityContainer.AddExtension(this);
        }

        #endregion

        #region IDependencyRegistrar Implementations

        public IDependencyRegistrar Register<TFrom, TTo>() where TTo : TFrom
        {
            unityContainer.RegisterType<TFrom, TTo>();
            return this;
        }

        public IDependencyRegistrar Register<TFrom, TTo>(string key) where TTo : TFrom
        {
            unityContainer.RegisterType<TFrom, TTo>(key);
            return this;
        }

        public IDependencyRegistrar Register(Type fromType, Type toType, params object[] parameterValues)
        {
            if (parameterValues == null || parameterValues.Length == 0)
                unityContainer.RegisterType(fromType, toType);
            else
                unityContainer.RegisterType(fromType, toType,
                    new InjectionConstructor(parameterValues)
                );
            return this;
        }

        public IDependencyRegistrar Register(Type fromType, Type toType, string key)
        {
            unityContainer.RegisterType(fromType, toType, key);
            return this;
        }

        public IDependencyRegistrar Register<TFrom>(TFrom instance)
        {
            unityContainer.RegisterInstance<TFrom>(instance);
            return this;
        }

        public IDependencyRegistrar Register<TFrom>(TFrom instance, string key)
        {
            unityContainer.RegisterInstance<TFrom>(key, instance);
            return this;
        }

        public IDependencyRegistrar Register(Type type, object instance)
        {
            unityContainer.RegisterInstance(type, instance);
            return this;
        }

        public IDependencyRegistrar Register(Type type, object instance, string key)
        {
            unityContainer.RegisterInstance(type, key, instance);
            return this;
        }

        public bool IsTypeRegistered(Type type, string key)
        {
            return unityContainer.IsRegistered(type, key);
        }

        public bool IsTypeRegistered(Type type)
        {
            return unityContainer.IsRegistered(type);
        }

        public bool IsTypeRegistered<TService>(string key)
        {
            return unityContainer.IsRegistered(typeof(TService), key);
        }

        public bool IsTypeRegistered<TService>()
        {
            return unityContainer.IsRegistered(typeof(TService));
        }

        public IDependencyRegistrar Register<TFrom>(TFrom instance, bool singleton)
        {
            LifetimeManager lifetimeManager = null;

            if (singleton)
                lifetimeManager = new ContainerControlledLifetimeManager();
            else
                lifetimeManager = new PerResolveLifetimeManager();

            unityContainer.RegisterInstance<TFrom>(instance, lifetimeManager);
            return this;
        }

        public IDependencyRegistrar Register<TFrom>(TFrom instance, string key, bool singleton)
        {
            LifetimeManager lifetimeManager = null;

            if (singleton)
                lifetimeManager = new ContainerControlledLifetimeManager();
            else
                lifetimeManager = new PerResolveLifetimeManager();

            unityContainer.RegisterInstance<TFrom>(key, instance, lifetimeManager);
            return this;
        }

        public IDependencyRegistrar Register(Type fromType, Type toType, bool singleton, params object[] parameterValues)
        {
            LifetimeManager lifetimeManager = null;

            if (singleton)
                lifetimeManager = new ContainerControlledLifetimeManager();
            else
                lifetimeManager = new PerResolveLifetimeManager();
            if( parameterValues == null || parameterValues.Length == 0 )
                unityContainer.RegisterType(fromType, toType, lifetimeManager);
            else
                unityContainer.RegisterType(fromType, toType, lifetimeManager, new InjectionConstructor(parameterValues));
            return this;
        }

        public IDependencyRegistrar Register<TFrom, TTo>(bool singleton) where TTo : TFrom
        {
            LifetimeManager lifetimeManager = null;

            if (singleton)
                lifetimeManager = new ContainerControlledLifetimeManager();
            else
                lifetimeManager = new PerResolveLifetimeManager();

            unityContainer.RegisterType(typeof(TFrom), typeof(TTo), lifetimeManager);
            return this;
        }

        public IDependencyRegistrar Register<TFrom, TTo>(string key, bool singleton) where TTo : TFrom
        {
            LifetimeManager lifetimeManager = null;

            if (singleton)
                lifetimeManager = new ContainerControlledLifetimeManager();
            else
                lifetimeManager = new PerResolveLifetimeManager();

            unityContainer.RegisterType(typeof(TFrom), typeof(TTo), key, lifetimeManager);
            return this;
        }

        public IDependencyRegistrar Register(Type fromType, Type toType, string key, bool singleton)
        {
            LifetimeManager lifetimeManager = null;

            if (singleton)
                lifetimeManager = new ContainerControlledLifetimeManager();
            else
                lifetimeManager = new PerResolveLifetimeManager();

            unityContainer.RegisterType(fromType, toType, key, lifetimeManager);
            return this;
        }

        #region FactoryMethod Register Methods

        public IDependencyRegistrar RegisterFactory<TService>(FactoryMethod<TService> factoryMethod)
        {
            unityContainer.RegisterType(typeof(TService),
                new InjectionFactory
                (
                    (container, requestedType, key) => factoryMethod(unityContainer.Resolve<IDependencyResolver>(), requestedType, key)
                )
            );
            return this;
        }

        public IDependencyRegistrar RegisterFactory<TService>(FactoryMethod<TService> factoryMethod, DependencyLifeStyle dependencyLifeStyle, string moduleName = null)
        {
            LifetimeManager lifetimeManager = new PerResolveLifetimeManager();
            switch (dependencyLifeStyle)
            {
                case DependencyLifeStyle.Transient:
                    lifetimeManager = new PerResolveLifetimeManager();
                    break;
                case DependencyLifeStyle.Singleton:
                    lifetimeManager = new ContainerControlledLifetimeManager();
                    break;
                case DependencyLifeStyle.UnitOfWorkScope:
                    lifetimeManager = new PerExecutionContextLifetimeManager();
                    break;
                case DependencyLifeStyle.ModularUnitOfWork:
                    if (string.IsNullOrEmpty(moduleName))
                        lifetimeManager = new PerExecutionContextLifetimeManager();
                    else
                        lifetimeManager = new PerModuleExecutionContextLifetimeManager(moduleName);
                    break;
                default:
                    break;
            }
            unityContainer.RegisterType(typeof(TService),
                lifetimeManager,
                new InjectionFactory
                (
                    (container, requestedType, key) => factoryMethod(unityContainer.Resolve<IDependencyResolver>(), requestedType, key)
                )
            );
            return this;
        }

        public IDependencyRegistrar RegisterFactory(Type genericType, FactoryMethod factoryMethod)
        {
            unityContainer.RegisterType(genericType,
                new InjectionFactory
                (
                    (container, requestedType, key) => factoryMethod(genericType, unityContainer.Resolve<IDependencyResolver>(), requestedType, key)
                )
            );
            return this;
        }

        public IDependencyRegistrar RegisterFactory(Type genericType, FactoryMethod factoryMethod, DependencyLifeStyle dependencyLifeStyle, string moduleName = null)
        {
            LifetimeManager lifetimeManager = new PerResolveLifetimeManager();
            switch (dependencyLifeStyle)
            {
                case DependencyLifeStyle.Transient:
                    lifetimeManager = new PerResolveLifetimeManager();
                    break;
                case DependencyLifeStyle.Singleton:
                    lifetimeManager = new ContainerControlledLifetimeManager();
                    break;
                case DependencyLifeStyle.UnitOfWorkScope:
                    lifetimeManager = new PerExecutionContextLifetimeManager();
                    break;
                case DependencyLifeStyle.ModularUnitOfWork:
                    if (string.IsNullOrEmpty(moduleName))
                        lifetimeManager = new PerExecutionContextLifetimeManager();
                    else
                        lifetimeManager = new PerModuleExecutionContextLifetimeManager(moduleName);
                    break;
                default:
                    break;
            }
            unityContainer.RegisterType(genericType,
                lifetimeManager,
                new InjectionFactory
                (
                    (container, requestedType, key) => factoryMethod(genericType, unityContainer.Resolve<IDependencyResolver>(), requestedType, key)
                )
            );
            return this;
        }

        public IDependencyRegistrar RegisterFactory(Type genericType, string key, FactoryMethod factoryMethod)
        {
            unityContainer.RegisterType(genericType, key,
                new InjectionFactory
                (
                    (container, requestedType, innerKey) => factoryMethod(genericType, unityContainer.Resolve<IDependencyResolver>(), requestedType, innerKey)
                )
            );
            return this;
        }

        #endregion

        #endregion

        #region UnityContainerExtension Implementations

        protected override void Initialize()
        {
        }

        #endregion
    }
}
